package com.example.demo.foundation;

import java.util.concurrent.CountDownLatch;

/**
 * 双重校验锁
 *
 * @author yunda
 * @date 2021/1/21 15:34
 */
public class VolatileTest {

    /**
     * volatile其中一个作用就是防止指令重排序（编译器重排序、处理器重排序及内存重排序（处理器缓存和读写缓冲））
     * 对于编译器，JMM的编译器重排序规则会禁止特定类型的编译器重排序（不是所有的编译器重排序都要禁止）。
     * 对于处理器重排序，JMM的处理器重排序规则会要求Java编译器在生成指令序列时，插入特定类型的内存屏障
     * （Memory Barriers，Intel称之为 Memory Fence）指令，通过内存屏障指令来禁止特定类型的处理器重排序
     *
     * Lock前缀的指令在多核处理器下会引发了两件事情
     *
     * 1）将当前处理器缓存行的数据写回到系统内存。
     * 2）这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。
     *
     * ********************************* 内存屏障类型表 *******************************************
     * 屏障类型                 指令示例                        说明
     * LoadLoad Barriers       Load1;LoadLoad;Load2           确保Load1数据的装载先于Load2及所有后续装载指令的装载
     * StoreStore Barriers     Store1;StoreStore;Store2       确保Store1数据对其他处理器可见（刷新到内存）先于Store2及后续存储指令的存储
     * LoadStore Barriers      Load1;LoadStore;Store2         确保Load1数据的装载先于先于Store2及后续存储指令刷新到内存
     * StoreLoad Barriers      Store1;StoreLoad;Load2         确保Store1数据对其他处理器可见（刷新到内存）先于Load2及所有后续装载指令的装载
     *                                                        StoreLoad Barrier会使该屏障之前的所有内存访问指令（存储和装载指令）完成之后
     *                                                        才执行该屏障之后的所有内存访问指令
     *
     *  StoreLoad Barriers是一个“全能型”的屏障，它同时具有其他3个屏障的效果。现代的多处理器大多支持该屏障
     * （其他类型的屏障不一定被所有处理器支持）。执行该屏障开销会很昂贵，因为当前处理器通常要把写缓冲区中的数
     *  据全部刷新到内存中（Buffer Fully Flush）。
     */
    private static volatile VolatileTest instance = null;

    private static VolatileTest getInstance() {
        if (instance == null) {
            synchronized (VolatileTest.class) {
                if (instance == null) {
                    // instance = new VolatileTest();
                    // 转换成汇编代码 0x01a3de1d: movb $0×0,0×1104800(%esi);0x01a3de24: lock addl $0×0,(%esp);
                    // 对象初始化流程  1、分配内存空间  2、初始化对象 3、将引用指向对象内存地址
                    //
                    // 为了提高处理速度，处理器不直接和内存进行通信，而是先将系统内存的数据读到内部缓存（L1，L2或其他）后再进行操作，
                    // 但操作完不知道何时会写到内存。被volatile修饰的对象，在初始化完成之后，jvm会向cpu发送lock指令（写屏障），
                    // 将缓存行数据刷新到主内存，其他处理利器通过嗅探总线上传播的数据，来检查自己缓存的值是否过期，如果发现缓存行对应
                    // 的内存地址数据被修改，将当前处理器缓存行设置成无效。当处理器对缓存行数据进行修改操作时，会强制从内存读取数据加载到缓存行

                    // lock指令会锁定内存区域的缓存并回写到内存，并使用缓存一致性机制来确保修改的原子性，此操作被称为“缓存锁定”，
                    // 缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据
                    instance = new VolatileTest();
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) {
        CountDownLatch count = new CountDownLatch(1);
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                System.out.println(getInstance());
                try {
                    count.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
        count.countDown();
    }

}
